package org.arl.modem.transportARQ;

import jade.core.behaviours.WakerBehaviour;

import java.util.BitSet;

public class RxTimer {
   private long infoRxTime;
   private TransportARQ tarq;

   private long infoACKTxTime;
   private long processingApproxTime;
   private long guardTime;
   private long propDelay = (long) (TransportARQ.propDelay * 1000);
   private long pktDuration;
   private BitSet bs;
   private WakerBehaviour bh;
   private int bsCounter = 0;
   private long wakeupTime;
   private long delta;
   private long stackTime;
   private long blockDuration = -1;
   private long startBlocktime = -1;

   /**
    * Timer for RX
    * @param infoRxTime - Info packet Arrival time (Phy oxco time)
    * @param tarq - Agent reference for logs
    * @param pktDuration - Data packet duration
    */
   public RxTimer(long infoRxTime, TransportARQ tarq, long pktDuration) {
      this.infoRxTime = infoRxTime / 1000;
      this.tarq = tarq;
      bs = new BitSet(TransportARQ.blockSize);
      this.pktDuration = pktDuration / 1000;
      this.guardTime = pktDuration / 2000;
      infoACKTxTime = -1;
   }

   public long getInfoACKTxTime() {
      return infoACKTxTime;
   }

   public void setInfoACKTxTime(long infoACKTxTime) {
      this.infoACKTxTime = infoACKTxTime / 1000;
      processingApproxTime = this.infoACKTxTime - this.infoRxTime
 - tarq.jarqPktDur;
      stackTime = processingApproxTime - tarq.rx_proctime_data;
      tarq.mylog.fine("tsp: approx Processing time:" + processingApproxTime);
      wakeupTime = getBlockGapTimeARQ();
      resetBehaviour();

   }

   @SuppressWarnings("serial")
   private void resetBehaviour() {
      if (bh != null) {
         bh.stop();
         tarq.removeBehaviour(bh);
         tarq.removeTimer(bh);
      }
      // wakeupTime = wakeupTime / 1000;
      tarq.mylog.fine("tsp: wakeup time for next RX is : " + wakeupTime);
      System.out.println("tsp: wakeup time for next RX is : " + wakeupTime);
      bh = new WakerBehaviour(this.tarq, wakeupTime) {
         long addedTime = System.currentTimeMillis();

         @SuppressWarnings("deprecation")
         @Override
         protected void handleElapsedTimeout() {
            if (wakeupTime != -1) {
               super.handleElapsedTimeout();
               long duration = System.currentTimeMillis() - addedTime;
               tarq.mylog.fine("tsp:RX Timer expired after duration!!!!!: "
                     + duration);
               System.out.println("tsp:RX Timer expired after duration!!!!!: "
                     + duration);
               switch (TransportARQ.ARQ_TYPE) {
               case 0:
                  setTimeForNext();
                  break;
               case 1:

                  break;
               case 2:
                  setTimeForNext();
                  break;
               case 3:

                  break;
               default:
                  break;
               }
               // setTimeForNext();
            }
         }

      };
      if (!tarq.rxHelper.checkIFComplete() && wakeupTime > 0)
         tarq.addBehaviour(bh);
   }

   private void setTimeForNext() {
      bsCounter++;
      if (bsCounter % TransportARQ.blockSize == 0) {
         switch (TransportARQ.ARQ_TYPE) {
         case 0:// fixed ARQ
            wakeupTime = getBlockGapTimeARQ() + guardTime;
            tarq.mylog.fine("tsp:wakeup calculated(ARQ):" + wakeupTime);
            break;
         case 1:// JARQ

            if (blockDuration == -1) {
               // blockDuration = (System.currentTimeMillis() - startBlocktime);
               blockDuration = tarq.blockSize * tarq.jarqPktDur;
               delta = (2 * propDelay + processingApproxTime) / tarq.k
 - blockDuration + guardTime // tarq.FIXED_PROC_TIME
               ;
               
               // (twoWayDelay - tarq.jarqPktdurControl + rx_procadjust) * 1000
               // / tarq.k
               // - this.blockTxTime - TransportARQ.THETA_TIME
               // * 1000;
            }
            System.out.println("prop:processing:blockdur:packetgaptime:" + 2 * propDelay + ":" + processingApproxTime + ":"
                  + blockDuration + ":" + getPakcetGapTime() / 2);
            wakeupTime = delta + getPakcetGapTime() / 2;
            break;
         case 2: // variable ARQ
            wakeupTime = getBlockGapTimeARQ() + guardTime;
            tarq.mylog.fine("tsp:wakeup calculated(ARQ):" + wakeupTime);
            break;
         case 3:// rateless ARQ

            if (blockDuration == -1) {
//               blockDuration = System.currentTimeMillis() - startBlocktime;
//               delta = (2 * propDelay + processingApproxTime) / tarq.k
//                     - blockDuration + guardTime;
               blockDuration = tarq.blockSize * tarq.jarqPktDur;
               delta = (2 * propDelay + processingApproxTime) / tarq.k - blockDuration + guardTime // tarq.FIXED_PROC_TIME
               ;
            }
            wakeupTime = delta + getPakcetGapTime() / 2;
            // System.out.println("wakeup time=" + wakeupTime);
            break;
         default:
            break;
         }
         tarq.sendAckMessage(200, tarq.rxSrc, tarq.rxDest, toByteArray(bs));
         bs.clear();
         if (TransportARQ.ARQ_TYPE == 2) {
            bs = new BitSet(TransportARQ.blockSize);
            bsCounter = 0;
         }

         if (tarq.rxHelper.checkIFComplete()) {
            shutdown();
         } else {
            resetBehaviour();
         }
      } else {
         wakeupTime = getPakcetGapTime() + guardTime;
         resetBehaviour();
      }

   }

   public long getBlockGapTimeARQ() {
      // long gap = 2 * propDelay + 2 * processingApproxTime + guardTime
      // + pktDuration;
      long gap = 2 * propDelay + tarq.jarqPktDur + tarq.rx_proctime_data + 2 * stackTime;
      // long gap = 2 * propDelay + 2 * processingApproxTime + 2 * guardTime + 2
      // * pktDuration;
      System.out.println("BLOCK wakeup time:" + gap);
      tarq.mylog.fine("BLOCK wakeuptime ARQ=" + gap);
      return gap;

   }

   public long getBlockGapTimeJARQ() {
      long gap = processingApproxTime + tarq.calc.get_m_jarq() * pktDuration;
      System.out.println("JARQ block time=" + gap);
      return gap;
   }

   public long getPakcetGapTime() {
      long gap = pktDuration;
      return gap;
   }

   public void blockInit() {
      bs.clear();
   }

   public void ackPacketatRX(long timestamp, TransportPacket tp) {
      if (TransportARQ.blockSize != 0 && TransportARQ.ARQ_TYPE != 3) {
         int setPos = bsCounter % TransportARQ.blockSize;
         tarq.mylog.fine("blockpackid=" + tp.blockPacketId + "set pos="
               + setPos);
         // bs.set(setPos);
         bs.set(tp.blockPacketId - 1);
         System.out.println("bscounter=" + bsCounter + "  bitset position="
               + setPos);
      }
      if (blockDuration == -1) {
         startBlocktime = System.currentTimeMillis();
      }
      setTimeForNext();

   }

   public void shutdown() {
      bh.stop();
      tarq.removeTimer(bh);
      tarq.removeBehaviour(bh);
      infoRxTime = -1;
      bs.clear();
      infoACKTxTime = -1;
      processingApproxTime = -1;
      guardTime = -1;
      pktDuration = -1;
      bsCounter = 0;
      wakeupTime = -1;
   }

   // Returns a byte array of at least length 1.
   // The most significant bit in the result is guaranteed not to be a 1
   // (since BitSet does not support sign extension).
   // The byte-ordering of the result is big-endian which means the most
   // significant bit is in element 0.
   // The bit at index 0 of the bit set is assumed to be the least significant
   // bit.
   public static byte[] toByteArray(BitSet bits) {
      int len = bits.length() / 8;
      if (bits.length() % 8.0 != 0) {
         len = len + 1;
      }
      byte[] bytes = new byte[len];
      for (int i = 0; i < bits.length(); i++) {
         if (bits.get(i)) {
            bytes[bytes.length - i / 8 - 1] |= 1 << (i % 8);
            // bytes[(i + 1) / 8] |= 1 << (i % 8);
         }
      }
      return bytes;
   }

}
